# 函数定义里面，函数的具体执行代码和return相关代码，都是需要缩进的
def add_3(num):
    new_number = num + 3
    return new_number  # 缩略版：return num + 3


"""
参数：外界向里面传值
返回值：里面的内容向外界传达
当函数调用时通过return向外返出值
注意：当函数有返回值时，必须要用一个变量名承接
"""


# 返回值接多个变量
def get_maxandmin(number):
    # 排序：冒泡排序
    for i in range(0, len(number) - 1):
        for j in range(0, len(number) - 1 - i):
            if number[j] > number[j + 1]:
                number[j], number[j + 1] = number[j + 1], number[j]
    # 获取头，尾
    max = number[-1]
    min = number[0]
    # 输出返回值
    return min, max  # (min,max)


list_1 = [34, 11, 78, 90, 100, 23, 56, 88, 91]
result = get_maxandmin(list_1)
print(result)

# 函数的调用
number1 = 10
number2 = add_3(number1)
print(number2)


# 必选传参
def absolute_value(num):
    """
    绝对值函数，函数可用循环定义
    :param num:必须是传入数字
    :return:返回该数字的绝对值
    """
    if num <= 0:
        return -num
    else:
        return num


print(absolute_value(-9))


# 在同时使用三种参数的时候定义函数有一定顺序：1.必选参数；2.默认参数；3.不定长参数
# 默认传参,默认值参数：在定义函数的时候，有一个或者多个参数已经赋好值
# 定义函数时默认值参数一定要在普通参数后面
def total_sum(x, y, init_sum=10):
    """
    连加三个参数的求和函数
    :param x:
    :param y:
    :param init_sum: 连加初始值
    :return:
    """
    init_sum += x + y
    return init_sum


print(total_sum(1, 8))  # init_sum参数值可加可不加，不加使用默认值


# 不定长参数（可变传参）
def sum_up(*num):
    sum_number = 0
    for counter in num:
        sum_number = sum_number + counter
    return sum_number


number_1 = sum_up(1, 2, 3, 4)
print(number_1)
number_2 = sum_up(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
print(number_2)


# 函数的返回值
def square(x, y):
    """
    对两个元素进行平方的函数
    :param x:
    :param y:
    :return: 两个元素的平方
    """
    x2 = x ** 2
    y2 = y ** 2
    return x2, y2  # 这种情况是自动分装到元组，return[x2, y2]可以分装到列表


result = square(5, 6)
print(result)
r1, r2 = square(5, 6)
print(r1, r2)

"""  
global关键字的添加：
只有不可变的类型才需要添加global
可变的类型不需要添加global
"""
a = 20  # a就是全局变量（函数外定义的变量）


def square_root_and_square():
    """
    对一个参数求平方根和另一个参数求平方的函数
    :return: 一个参数的平方根和另一个参数的平方
    """
    b = 100  # b就是局部变量（函数内定义的变量）
    print(a)
    return b ** (1 / 2), a ** 2


print(square_root_and_square())  # print(b)是报错的，因为b在函数外没有定义，局部变量的作用范围仅限函数内部

# 对全局变量的修改
"""
函数内部可以直接使用全局变量，但是不能直接修改全局变量
如果想要修改全局变量，则必须使用关键字global 全局变量名
"""


def some():
    global a  # 声明现在a的值采用全局变量的值
    a = 30  # 对全局变量a的值进行的修改
    print(a)


some()  # 在前面有全局变量a和当前函数局部变量a冲突时，会优先选择当前局部变量
print(a)  # 在函数外部打印a的值就会采用全局变量的数据


def test_1(a, lst=[1, 2]):
    if a not in lst:
        lst.append(a)
    return lst


# 使用可变对象（如列表）作为函数的默认参数会在函数调用之间会保持状态，
# 意味着调用多次test_1可能会在lst中累积值
print(f"第一次调用test_1函数输出的结果是：{test_1(10)}")
# 正解是1,2,10,20    易错认为1,2,20
print(f"第二次调用test_1函数输出的结果是：{test_1(20)}")
print(f"第三次调用test_1函数输出的结果是：{test_1(30, lst=[60, 70])}")
# 前面的lst只是临时修改了内存地址，出函数后列表数据依旧不变
print(f"第二次调用test_1函数输出的结果是：{test_1(40)}")

"""
递归函数：在内部不调用其他的函数，而是调用自己本身的函数
遵循：1.必须要有出口
     2.每次递归向出口靠近
"""


def factorial(num: int) -> int:
    """
    计算阶乘的函数
    :param num:
    :return:
    """
    if num == 1:
        return 1  # 递归函数退出的出口（递归有最大次数限制，若量大的话可用循环）
    return num * factorial(num - 1)


print(factorial(5))

"""
匿名函数：
lambda 参数列表：返回值运算表达式
lambda函数能接收任何数量的参数但只能返回一个表达式的值
使用场合：作为函数的参数使用
"""
# 使用匿名函数计算两个函数的和
fn = lambda x, y: x + y
print(fn(2, 9))  # 调用匿名函数需要使用变量填装

lst = [
    {"name": "张三", "age": 34},
    {"name": "李四", "age": 16},
    {"name": "王五", "age": 41}
]
# 根据年龄来排序(升序)
lst.sort(key=lambda item: item["age"])
# 降序排序
lst.sort(key=lambda item: item["age"], reverse=True)
print(lst)

"""
高阶函数：
一个函数的参数是另一个函数
"""
# 将列表中的元组按年龄顺序倒序排列
list_3 = [("tom", 19), ("tony", 20), ("lily", 18), ("daniel", 21), ("rose", 22)]
s = sorted(list_3, key=lambda x: x[1], reverse=True)
print(s)

# filter函数（输出依据为布尔数，符合标准（函数）为true就输出，false则返回）
list_3 = [("tom", 19), ("tony", 20), ("lily", 18), ("daniel", 21), ("rose", 22)]
r = list(filter(lambda x: x[1] > 20, list_3))  # 将列表中年龄大于20的元组挑出来创立一个列表
print(r)

# 案例：将偶数从列表中过滤出来
lst1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(list(filter(lambda n: n % 2 == 0, lst1)))

"""
map函数（映射）：输入的第一个参数是所用函数，第二个则是所需要加工的数据,所输出的数据是可迭代对象
括号中间需要加入list对map可迭代对象进行转化成列表
作用：通过匿名函数指明提取的内容，并对内容进行加工
"""
list_3 = [("tom", 19), ("tony", 20), ("lily", 18), ("daniel", 21), ("rose", 22)]
ma = map(lambda x: x[0].title, list_3)
print(list(ma))

print(list(map(lambda n: n ** 2, [1, 2, 3, 4, 5, 6])))

from functools import reduce  # reduce函数需要导入，函数必须是有两个参数

# 累加的过程，先是2+4=6，再是6+6=12依次累加
print(reduce(lambda x, y: x + y, [2, 4, 6, 8, 10]))
# 将10放在第三个参数的位置（初始值）表示从10开始累加
print(reduce(lambda x, y: x + y, [2, 4, 6, 8], 10))


# 第一种高阶函数：函数的参数是函数
def some_number(a, b, f):
    """

    :param a:
    :param b:
    :param f:对两个数值进行整理的函数
    :return:
    """
    return f(a) + f(b)


# abs后面不用接(),因为是调函数而不是调输出结果
print(some_number(1, -6, abs))  # 通过绝对值整理之后再求和
# 通过平方整理之后再求和
print(some_number(5, 6, lambda n: n ** 2))


# 第二种高阶函数：函数的返回值是函数
def text_2(*args):
    def sum_nums():
        sum = 0
        for n in args:
            sum += n
        return sum

    return sum_nums  # 后面不用加()，因为是把函数返回，并不是把函数调用后返回


print(text_2(5, 6, 7, 8))  # 返回的是一个函数，需要再对函数进行处理
# 后面加上一个()是再次调用前一个返回的函数来累加
print(text_2(5, 6, 7, 8)())

# 案例：统计长字符串中的单词出现次数
str1 = "hello world hello java hello python python good"
# 做法一
# 第一步：将单词切开
lst = str1.split(" ")
print(lst)
# 第二步：每个单词只要出现了，那么就代表有一次
# 例如字典型['hello':1,'world':1,...]
new_lst = list(map(lambda item: {item: 1}, lst))
print(new_lst)


# 第三步：调用reduce函数实现相同函数的叠加
def funct(dict1, dict2):
    # 将dict1作为叠加的返回字典
    key = list(dict2.items())[0][0]  # 得到dict2中的key（单词：world）
    value = list(dict2.items())[0][1]  # 得到dict2中的value（1）
    dict1[key] = dict1.get(key, 0) + value
    return dict1


# 第四步：打印输出结果列表
print(reduce(funct, new_lst))

# 做法二
# 高阶函数（函数式编程）使用了大量函数，减少了代码的重复，程序比较短，提高了开发效率
print(reduce(funct, list(map(lambda item: {item: 1}, str1.split(" ")))))

# sorted函数
lst = [
    {"name": "张三", "age": 34},
    {"name": "李四", "age": 16},
    {"name": "王五", "age": 41}
]
# 根据年龄排序
print(sorted(lst, key=lambda item: item['age']))
# 降序排列
print(sorted(lst, key=lambda item: item['age'], reverse=True))

str_lst = ["python", "java", "hello", "world"]
# upper函数前面必须带被调用类型，因为upper函数不是内置函数
print(sorted(str_lst, key=str.upper))


# 判断参数是否是某种类型   isinstance(变量，类型） ---> 返回值是bool
def get_sum_1(a, b):
    if isinstance(a, int) and isinstance(b, int):
        s = a + b
        print(s)
    else:
        print("类型错误！")


get_sum_1(2, 3)
get_sum_1('hello', 'world')


def get_sum_2(*args):  # 此时是装包操作
    s = 0
    for i in args:
        s += i
    print("和:", s)


ran_list = [23, 55, 67, 48, 51, 89, 50]
"""
拆包：list,tuple,set
调用的时候：
函数(*list)   函数(*tuple)   函数(*set)
"""
print(*ran_list)  # 一颗星是元组集合列表的拆包

"""
可变参数：**kwargs
在函数调用的时候必须传递关键字参数，
才可以将其转换成{key,value}的形式装到字典中
"""


def show_book(**kwargs):
    print(kwargs)
    for k, v in kwargs.items():
        print(k, v)


show_book()
show_book(book="仙逆", number=1)

book = {"book": "仙逆", "author": "耳根", "number": 5}
show_book(**book)  # 两颗星才是字典的拆包


# 内部函数也看做一个局部变量
def outer():
    a = 100

    def inner():
        b = 200
        # b += a,内部函数可以使用外部函数的变量
        nonlocal a  # 如果想要修改外部函数的变量，需要在内部函数中添加：nonlocal
        a += b  # 内部函数不能修改外部函数的变量
        print("我是内部函数", b)

    # result = locals(),locals()表示查看函数中的局部变量，以字典的形式返回
    # 查看全局变量：globals(),返回值是一个字典，分成1.系统 2.自定义
    # print(result)
    print(a)
    # 调用inner
    inner()


# 查找顺序：内层函数-->外层函数-->全局变量-->系统值builtins
outer()

"""
闭包（主要用于装饰器）：
1.嵌套函数
2.内部函数引用了外部函数的变量
3.返回值是内部函数
"""


def outer_1(n):
    a = 10

    def inner_1():
        b = a + n
        print("内部函数：", b)

    return inner_1


r = outer_1(5)
print(r)
r()  # 为了调用内部函数


# 定义装饰器
def decorator(func):
    def wrapper():
        func()
        print("刷漆")
        print("铺地板")
        print("买家具")
        print("精装修房子")

    return wrapper  # 别加()，加了表示函数调用


@decorator  # house = decorator（house）
def house():
    print("毛坯房...")


"""
带参数的装饰器:
如果原函数有参数，则装饰器内部函数也要有参数
"""


def decorator(func):
    def wrapper(address):  # address = “北京四合院”
        func(address)  # func就是house
        print("刷漆")
        print("铺地板")
        print("买家具")
        print("精装修房子")

    return wrapper


@decorator
def house(address):
    print(f"房子的地址在{address}，是一个毛坯房...")


house("北京四合院")  # house就是wrapper


# 多个参数下的装饰器
def decorator(func):
    def wrapper(*args, **kwargs):  # 加**kwargs是为了应付关键字参数的出现
        func(*args, **kwargs)
        print("刷漆")
        print("铺地板")
        print("买家具")
        print("精装修房子")

    return wrapper


@decorator  # house = decorator（house）
def changfang(address, area):
    print(f"房子地址在：{address}，是个毛坯房，建筑面积是：{area}平方米")


changfang("邵阳邵东西站", 1000)  # changfang就是wrapper

"""
装饰器修饰有返回值的函数:
如果原函数有返回值，则装饰器内部函数也要有返回值
"""


def decorator(func):
    def wrapper(*args, **kwargs):
        fee = func(*args, **kwargs)
        print(f"预计花费费用{fee}")
        print("刷漆")
        print("铺地板")
        print("买家具")
        print("精装修房子")
        return 60000

    return wrapper


@decorator
def house():
    print("这是一个毛坯房...")
    return 50000


fee = house()
print(fee)
